package com.wzw.basics.concurrent.lock.support;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * LockSupport 提供新版性能与功能更加强大的线程等待唤醒机制
 *
 * @author Wangzhiwen
 */
public class Support {

    public static void main(String[] args) {
        WaitNotify.m1();
        AwaitSignal.m1();
        LockSupportD.m1();
    }

}

/**
 * Object 对线程通信的支持。
 * 铁三角：Synchronized Object.wait() Object.notify() 必须同时存在且在synchronized内才能使用。
 * wait和notify方法必须要在同步块或者方法里面且成对出现使用，否则会抛出java.lang.IllegalMonitorStateException。
 * 唤醒和等待的顺序不能颠倒
 */
class WaitNotify {

    private static final Object lock = new Object();

    public static void m1() {
        new Thread(() -> {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " come in");
                // 进入等待
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    // ignore
                }
                System.out.println(Thread.currentThread().getName() + " notify");
            }
        }, "Thread A").start();

        new Thread(() -> {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " come in");
                // 唤醒线程
                lock.notify();
                System.out.println(Thread.currentThread().getName() + " exit");
            }
        }, "Thread B").start();
    }

}

/**
 * lock.newCondition(); 对线程通信的支持
 * 铁三角：Lock.lock() && Lock.unlock() condition.await() condition.signal() 必须在上锁与解锁之间使用
 * await和signal方法必须要在同步块或者方法里面且成对出现使用，否则会抛出java.lang.IllegalMonitorStateException。
 * 唤醒和等待的顺序不能颠倒
 */
class AwaitSignal {
    private static final Lock lock = new ReentrantLock();
    private static final Condition condition = lock.newCondition();

    public static void m1() {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " come in");
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                // ignore
            }finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " signal");
        },"Thread A").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " come in");
            lock.lock();
            condition.signal();
            System.out.println(Thread.currentThread().getName() + " exit");
            lock.unlock();
        },"Thread B").start();
    }
}

/**
 * LockSupport 对线程通信的支持
 * LockSupport是用来创建锁和共他同步类的基本线程阻塞原语。
 * LockSupport是一个线程阻塞工具类，所有的方法都是静态方法，可以让线程在任意位置阻塞，阻寨之后也有对应的唤醒方法。归根结底，LockSupport调用的Unsafe中的native代码。
 * LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
 * LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1，0的开关，默认是0，
 * <p>
 * 内部全是静态方法，底层是unsafe的支撑
 * LockSupport.park() 阻塞
 * LockSupport.unpark() 唤醒
 * <p>
 * 原理解析：
 * permit 值为0或1的许可凭证
 * 初始值为 0
 * park方法会消耗凭证，即要对凭证减一。当凭证不为1时，就会阻塞当前线程
 * unpark方法会积累凭证，既要对凭证加一。当凭证加一之后不为0了，park方法会立马消费凭证。当前线程会被唤醒
 * 每个线程都有一个相关的permit, permit最多只有一个，重复调用unpark也不会积累凭证。
 * <p>
 * 实例题：
 * 为什么可以先唤醒线程后阻塞线程？
 * 因为unpark获得了一个凭证，之后再调用park方法，就可以名正言顺的凭证消费，故不会阻塞。
 * 为什么唤醒两次后阻塞两次，但最终结果还会阻塞线程？
 * 因为凭证的数量最多为1（不能累加），连续调用两次 unpark和调用一次 unpark效果一样，只会增加一个凭证；而调用两次park却需要消费两个凭证，证不够，不能放行。
 */
class LockSupportD {

    public static void m1() {
        Thread threadA = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " unpark");
        }, "Thread A");
        threadA.start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " come in");
            LockSupport.unpark(threadA);
            System.out.println(Thread.currentThread().getName() + " exit");
        },"Thread B").start();
    }

}
